﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;

namespace YasharEl.Infrastructure.EntityModel
{
    /// <summary>
    /// http://lostechies.com/jimmybogard/2008/08/12/enumeration-classes/
    /// </summary>
    [Serializable]
    public abstract class Enumeration : IComparable, IEnumerationFactory
    {
        #region Private ReadOnly Fields

        private readonly int _value;
        private readonly string _shortName;
        private readonly string _longName;

        #endregion

        #region Constructors

        protected Enumeration()
        {
        }

        protected Enumeration(int value, string shortName,string longName)
        {
            _value = value;
            _shortName = shortName;
            _longName = longName;
        }

        #endregion

        #region ReadOnly Properties

        public int Value
        {
            get { return _value; }
        }

        public string ShortName
        {
            get { return _shortName; }
        }

        public string LongName
        {
            get { return _longName; }
        }

        #endregion

        #region Object Overrides

        public override string ToString()
        {
            return string.Format("Enumeration({0},{1} with value {2}.", ShortName, LongName, Value);
        }

        public override bool Equals(object obj)
        {
            var otherValue = obj as Enumeration;

            if (otherValue == null)
            {
                return false;
            }

            var typeMatches = GetType().Equals(obj.GetType());
            var valueMatches = _value.Equals(otherValue.Value);

            return typeMatches && valueMatches;
        }

        public override int GetHashCode()
        {
            return _value.GetHashCode();
        }

        #endregion

        #region Public Static Methods

        public static IEnumerable<T> GetAll<T>() where T : Enumeration
        {
            var type = typeof(T);
            var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);

            foreach (var info in fields)
            {
                T locatedValue = null;
                if (!type.IsAbstract)
                {
                    var instance = (T)FormatterServices.GetUninitializedObject(type);
                    locatedValue = info.GetValue(instance) as T;
                }
                else
                {
                    // Pass null parameter because it static field.
                    locatedValue = info.GetValue(null) as T;
                }

                if (locatedValue != null)
                {
                    yield return locatedValue;
                }
            }
        }

        public static IEnumerable GetAll(Type type)
        {
            var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);

            foreach (var info in fields)
            {
                object instance = Activator.CreateInstance(type);
                yield return info.GetValue(instance);
            }
        }

        public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue)
        {
            var absoluteDifference = Math.Abs(firstValue.Value - secondValue.Value);
            return absoluteDifference;
        }

        public static T FromValue<T>(int value) where T : Enumeration//, new()
        {
            var matchingItem = parse<T, int>(value, "value", item => item.Value == value);
            return matchingItem;
        }

        public static T FromShortName<T>(string shortName) where T : Enumeration//, new()
        {
            var matchingItem = parse<T, string>(shortName, "short name", item => item.ShortName == shortName);
            return matchingItem;
        }

        #endregion

        #region Private Static Methods

        private static T parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration//, new()
        {
            var matchingItem = GetAll<T>().FirstOrDefault(predicate);

            if (matchingItem == null)
            {
                throw new InvalidEnumerationException(value,description, typeof(T));
            }

            return matchingItem;
        }

        #endregion

        #region IComparable Implementations

        public virtual int CompareTo(object other)
        {
            return Value.CompareTo(((Enumeration)other).Value);
        }

        #endregion

        #region IEnumerationFactory Implementations

        public EnumerationType CreateEnumeration<EnumerationType>() where EnumerationType : Enumeration
        {
            return (EnumerationType)this;
        }

        #endregion
    }

    public interface IEnumerationFactory
    {
        EnumerationType CreateEnumeration<EnumerationType>() where EnumerationType : Enumeration;
    }
}
